package jp.wug.core;

import com.google.inject.TypeLiteral;
import jp.wug.Renderer;
import jp.wug.util.TypeLiteralUtil;

import java.lang.reflect.Method;
import java.util.*;

class Translators {
    static final Translators TRANSLATORS = Translators.create();
    private static final Method ITERABLE_ITERATOR;
    private static final Method ITERATOR_NEXT;
    private static final Method MAP_ENTRY_KEY;
    private static final Method MAP_ENTRY_VALUE;
    private static final Method RENDERER_RENDER_METHOD;

    static {
        try {
            ITERABLE_ITERATOR = Iterable.class.getMethod("iterator");
            ITERATOR_NEXT = Iterator.class.getMethod("next");
            MAP_ENTRY_KEY = Map.Entry.class.getMethod("getKey");
            MAP_ENTRY_VALUE = Map.Entry.class.getMethod("getValue");
            RENDERER_RENDER_METHOD = Renderer.class.getMethod("render", Object.class, Object.class);
        } catch (NoSuchMethodException e) {
            throw new AssertionError(e);
        }
    }

    private final Map<Tuple<TypeLiteral<?>, TypeLiteral<?>>, Translator<?, ?>> translators;

    Translators() {
        translators = new HashMap<Tuple<TypeLiteral<?>, TypeLiteral<?>>, Translator<?, ?>>();
    }

    private static Translators create() {
        Translators translators = new Translators();

        translators.put(new StringsToStringTranslator(), String[].class, String.class);
        Translator<String, Integer> stringToIntegerTranslator = new StringToIntegerTranslator();
        translators.put(stringToIntegerTranslator, String.class, Integer.class);
        translators.put(new StringsToTranslator<Integer>(stringToIntegerTranslator), String[].class, Integer.class);
        translators.put(new StringsToTranslator<Integer>(stringToIntegerTranslator), String[].class, int.class);
        translators.put(new StringArrayToListTranslator<Integer>(stringToIntegerTranslator), TypeLiteral.get(String[].class), new TypeLiteral<List<Integer>>() {
        });

        translators.put(new StringToStringsTranslator(), String.class, String[].class);
        Translator<Integer, String> integerToStringTranslator = new IntegerToStringTranslator();
        translators.put(stringToIntegerTranslator, String.class, int.class);
        translators.put(new ToStringsTranslator<Integer>(integerToStringTranslator), Integer.class, String[].class);
        translators.put(new ToStringsTranslator<Integer>(integerToStringTranslator), int.class, String[].class);
        translators.put(new ListToStringArrayTranslator<Integer>(integerToStringTranslator), new TypeLiteral<List<Integer>>() {
        }, TypeLiteral.get(String[].class));

        return translators;
    }

    private static class Tuple<T, U> {
        private final T t;
        private final U u;

        Tuple(T t, U u) {
            this.t = t;
            this.u = u;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Tuple<?, ?>) {
                Tuple<?, ?> target = (Tuple<?, ?>) obj;
                return t.equals(target.t) && u.equals(target.u);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return t.hashCode() ^ -u.hashCode();
        }
    }

    interface Translator<T, U> {
        U translate(T t);
    }

    <T, U> void put(Translator<T, U> translator, Class<T> input, Class<U> output) {
        put(translator, TypeLiteral.get(input), TypeLiteral.get(output));
    }

    <T, U> void put(Translator<T, U> translator, TypeLiteral<T> input, TypeLiteral<U> output) {
        translators.put(new Tuple<TypeLiteral<?>, TypeLiteral<?>>(input, output), translator);
    }

    <T, U> boolean contains(TypeLiteral<T> input, TypeLiteral<U> output) {
        if (TypeLiteralUtil.isAssignable(input, output)) {
            return true;
        } else if (output.equals(TypeLiteral.get(void.class))) {
            return true;
        } else {
            Translator<?, ?> translator = translators.get(new Tuple<TypeLiteral<?>, TypeLiteral<?>>(input, output));
            if (translator != null) {
                return true;
            } else if (output.equals(TypeLiteral.get(String.class))) {
                return true;
            } else {
                return false;
            }
        }
    }

    @SuppressWarnings("unchecked")
    <T, U> Translator<T, U> get(TypeLiteral<T> input, TypeLiteral<U> output, Collection<ModelFactory> modelFactories) {
        if (TypeLiteralUtil.isAssignable(input, output)) {
            return new CastTranslator();
        } else if (output.equals(TypeLiteral.get(void.class))) {
            return (Translator<T, U>) new VoidTranslator<T>();
        } else if (output.equals(TypeLiteral.get(Model.class))) {
            for (ModelFactory modelFactory : modelFactories) {
                if (TypeLiteralUtil.isAssignable(input, modelFactory.getType())) {
                    return new ObjectToModelTranslator(input, modelFactory);
                }
            }
            throw new IllegalArgumentException("No Translator found: " + input + " to " + output);
        } else if (input.equals(TypeLiteral.get(Model.class))) {
            for (ModelFactory modelFactory : modelFactories) {
                if (TypeLiteralUtil.isAssignable(output, modelFactory.getType())) {
//                    return new ObjectToModelTranslator(output, modelFactory);
                }
            }
            throw new IllegalArgumentException("No Translator found: " + input + " to " + output);
        } else if (Iterable.class.isAssignableFrom(input.getRawType()) && Iterable.class.isAssignableFrom(output.getRawType())) {
            TypeLiteral<?> i = input.getReturnType(Translators.ITERABLE_ITERATOR).getReturnType(Translators.ITERATOR_NEXT);
            TypeLiteral<?> o = output.getReturnType(Translators.ITERABLE_ITERATOR).getReturnType(Translators.ITERATOR_NEXT);
            @SuppressWarnings("unchecked")
            Translator<T, U> translator = (Translator<T, U>) createIterableTranslator(i, o, modelFactories);
            return translator;
        } else if (Map.Entry.class.isAssignableFrom(input.getRawType()) && Map.Entry.class.isAssignableFrom(output.getRawType())) {
            TypeLiteral<?> inputKey = input.getReturnType(Translators.MAP_ENTRY_KEY);
            TypeLiteral<?> inputValue = input.getReturnType(Translators.MAP_ENTRY_VALUE);
            TypeLiteral<?> outputKey = output.getReturnType(Translators.MAP_ENTRY_KEY);
            TypeLiteral<?> outputValue = output.getReturnType(Translators.MAP_ENTRY_VALUE);
            @SuppressWarnings("unchecked")
            Translator<T, U> translator = (Translator<T, U>) new MapEntryTranslator(get(inputKey, outputKey, modelFactories), get(inputValue, outputValue, modelFactories));
            return translator;
        } else if (Renderer.class.isAssignableFrom(input.getRawType()) && Renderer.class.isAssignableFrom(output.getRawType())) {
            List<TypeLiteral<?>> inputParameters = input.getParameterTypes(Translators.RENDERER_RENDER_METHOD);
            List<TypeLiteral<?>> outputParameters = output.getParameterTypes(Translators.RENDERER_RENDER_METHOD);
            Translator<?, ?> keyTranslator = get(outputParameters.get(0), inputParameters.get(0), modelFactories);
            Translator<?, ?> valueTranslator = get(outputParameters.get(1), inputParameters.get(1), modelFactories);
            return new RendererTranslator(keyTranslator, valueTranslator);
        } else {
            Translator<T, U> translator = (Translator<T, U>) translators.get(new Tuple<TypeLiteral<?>, TypeLiteral<?>>(input, output));
            if (translator != null) {
                return translator;
            } else if (output.equals(TypeLiteral.get(String.class))) {
                return (Translator<T, U>) new ToStringTranslator<T>();
            }
        }
        throw new IllegalArgumentException("No Translator found: " + input + " to " + output);
    }

    private <T, U> Translator<Iterable<T>, Iterable<U>> createIterableTranslator(TypeLiteral<T> t, TypeLiteral<U> u, Collection<ModelFactory> modelFactories) {
        return new IterableTranslator<T, U>(get(t, u, modelFactories));
    }

    static <T, U> Translator<T, U> getTranslator(TypeLiteral<T> input, TypeLiteral<U> output) {
        return Translators.TRANSLATORS.get(input, output, Arrays.<ModelFactory>asList());
    }

    private static class ObjectToModelTranslator<T> implements Translator<T, Model> {
        private final TypeLiteral<T> type;
        private final ModelFactory factory;

        ObjectToModelTranslator(TypeLiteral<T> type, ModelFactory factory) {
            this.type = type;
            this.factory = factory;
        }

        public Model translate(Object object) {
            return factory.create(object);
        }
    }

    private static class MapEntryTranslator<T1, T2, U1, U2> implements Translator<Map.Entry<T1, U1>, Map.Entry<T2, U2>> {
        private final Translator<T1, T2> keyTranslator;
        private final Translator<U1, U2> valueTranslator;

        private MapEntryTranslator(Translator<T1, T2> keyTranslator, Translator<U1, U2> valueTranslator) {
            this.keyTranslator = keyTranslator;
            this.valueTranslator = valueTranslator;
        }

        public Map.Entry<T2, U2> translate(Map.Entry<T1, U1> entry) {
            return new MapEntryImpl<T2, U2>(keyTranslator.translate(entry.getKey()), valueTranslator.translate(entry.getValue()));
        }
    }

    private static class MapEntryImpl<K, V> implements Map.Entry<K, V> {
        private final K k;
        private final V v;

        private MapEntryImpl(K k, V v) {
            this.k = k;
            this.v = v;
        }

        public K getKey() {
            return k;
        }

        public V getValue() {
            return v;
        }

        public V setValue(V value) {
            throw new UnsupportedOperationException();
        }
    }

    private static class IterableTranslator<T, U> implements Translator<Iterable<T>, Iterable<U>> {
        private final Translator<T, U> translator;

        private IterableTranslator(Translator<T, U> translator) {
            this.translator = translator;
        }

        public Iterable<U> translate(Iterable<T> iterable) {
            return new TranslatingIterable<T, U>(iterable, translator);
        }
    }

    private static class TranslatingIterable<T, U> implements Iterable<U> {
        private final Iterable<T> iterable;
        private final Translator<T, U> translator;

        private TranslatingIterable(Iterable<T> iterable, Translator<T, U> translator) {
            this.iterable = iterable;
            this.translator = translator;
        }

        public Iterator<U> iterator() {
            return new TranslatingIterator<T, U>(iterable.iterator(), translator);
        }
    }

    private static class TranslatingIterator<T, U> implements Iterator<U> {
        private final Iterator<T> iterator;
        private final Translator<T, U> translator;

        private TranslatingIterator(Iterator<T> iterator, Translator<T, U> translator) {
            this.iterator = iterator;
            this.translator = translator;
        }

        public boolean hasNext() {
            return iterator.hasNext();
        }

        public U next() {
            return translator.translate(iterator.next());
        }

        public void remove() {
            iterator.remove();
        }
    }

    private static class RendererTranslator<K1, K2, V1, V2> implements Translator<Renderer<K2, V2>, Renderer<K1, V1>> {
        private final Translator<K1, K2> keyTranslator;
        private final Translator<V1, V2> valueTranslator;

        private RendererTranslator(Translator<K1, K2> keyTranslator, Translator<V1, V2> valueTranslator) {
            this.keyTranslator = keyTranslator;
            this.valueTranslator = valueTranslator;
        }

        public Renderer<K1, V1> translate(Renderer<K2, V2> renderer) {
            return new RendererWrapper<K1, K2, V1, V2>(keyTranslator, valueTranslator, renderer);
        }
    }

    private static class RendererWrapper<K1, K2, V1, V2> implements Renderer<K1, V1> {
        private final Translator<K1, K2> keyTranslator;
        private final Translator<V1, V2> valueTranslator;
        private final Renderer<K2, V2> renderer;

        private RendererWrapper(Translator<K1, K2> keyTranslator, Translator<V1, V2> valueTranslator, Renderer<K2, V2> renderer) {
            this.keyTranslator = keyTranslator;
            this.valueTranslator = valueTranslator;
            this.renderer = renderer;
        }

        public void render(K1 key, V1 value) {
            renderer.render(keyTranslator.translate(key), valueTranslator.translate(value));
        }
    }

    private static class StringToStringsTranslator implements Translator<String, String[]> {
        public String[] translate(String t) {
            return new String[]{t};
        }
    }

    private static class StringsToStringTranslator implements Translator<String[], String> {
        public String translate(String[] strings) {
            if (strings.length == 0) {
                return null;
            }
            return strings[0];
        }
    }

    private static class StringsToTranslator<T> implements Translator<String[], T> {
        private final Translator<String, T> translator;

        private StringsToTranslator(Translator<String, T> translator) {
            this.translator = translator;
        }

        public T translate(String[] strings) {
            if (strings.length == 0) {
                return null;
            }
            return translator.translate(strings[0]);
        }
    }

    private static class ToStringsTranslator<T> implements Translator<T, String[]> {
        Translator<T, String> translator;

        private ToStringsTranslator(Translator<T, String> translator) {
            this.translator = translator;
        }

        public String[] translate(T t) {
            return new String[]{translator.translate(t)};
        }
    }

    private static class IntegerToStringTranslator implements Translator<Integer, String> {
        public String translate(Integer t) {
            return t.toString();
        }
    }

//    private static class StringsToIntegerTranslator implements Translator<String[], Integer> {
//        public Integer translate(String[] strings) {
//            if (strings.length == 0) {
//                return null;
//            }
//            return Integer.parseInt(strings[0]);
//        }
//    }

    private static class StringToIntegerTranslator implements Translator<String, Integer> {
        public Integer translate(String t) {
            return Integer.parseInt(t);
        }
    }

    private static class ListToStringArrayTranslator<T> implements Translator<List<T>, String[]> {
        private final Translator<T, String> translator;

        private ListToStringArrayTranslator(Translator<T, String> translator) {
            this.translator = translator;
        }

        public String[] translate(List<T> ts) {
            String[] array = new String[ts.size()];
            for (int i = 0; i < array.length; i++) {
                array[i] = translator.translate(ts.get(i));
            }
            return array;
        }
    }

    private static class StringArrayToListTranslator<T> implements Translator<String[], List<T>> {
        private final Translator<String, T> translator;

        private StringArrayToListTranslator(Translator<String, T> translator) {
            this.translator = translator;
        }

        public List<T> translate(String[] strings) {
            List<T> list = new ArrayList<T>(strings.length);
            for (int i = 0; i < strings.length; i++) {
                list.add(translator.translate(strings[i]));
            }
            return list;
        }
    }


//    private static class StringsToStringsTranslator implements Translator<String[], String[]> {
//        public String[] translate(String[] t) {
//            return t.clone();
//        }
//    }

    private static class ToStringTranslator<T> implements Translator<T, String> {
        public String translate(T t) {
            return t.toString();
        }
    }

    private static class CastTranslator<T extends U, U> implements Translator<T, U> {
        public U translate(T t) {
            return t;
        }
    }

    private static class VoidTranslator<T> implements Translator<T, Void> {
        public Void translate(T t) {
            return null;
        }
    }
}
